﻿using Asp.Versioning.ApiExplorer;
using Kernel.Buildin.Swagger;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace WebAPI.Configure.Swagger;

/// <summary>
/// Configures the Swagger generation options.
/// </summary>
/// <remarks>This allows API versioning to define a Swagger document per API version after the
/// <see cref="IApiVersionDescriptionProvider"/> service has been resolved from the service container.</remarks>
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
    readonly IApiVersionDescriptionProvider provider;

    /// <summary>
    /// Initializes a new instance of the <see cref="ConfigureSwaggerOptions"/> class.
    /// </summary>
    /// <param name="provider">The <see cref="IApiVersionDescriptionProvider">provider</see> used to generate Swagger documents.</param>
    public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => this.provider = provider;

    /// <summary>
    /// 持久化api分组信息
    /// </summary>
    static SortedDictionary<(string, string), bool> apiInclus = new SortedDictionary<(string, string), bool>();

    /// <inheritdoc />
    public void Configure(SwaggerGenOptions options)
    {
        #region 分组方案
        List<string> groups = new List<string>();
        //遍历ApiGroupNames所有枚举值生成接口文档，Skip(1)是因为Enum第一个FieldInfo是内置的一个Int值
        typeof(ApiGroupNames).GetFields(BindingFlags.Static | BindingFlags.Public).ToList().ForEach(f =>
        {
            //获取枚举值上的特性
            var info = f.GetCustomAttributes(typeof(GroupInfoAttribute), false).OfType<GroupInfoAttribute>().FirstOrDefault();

            var openApiInfo = new OpenApiInfo();
            openApiInfo.Title = info?.Title;
            openApiInfo.Version = f.Name;
            openApiInfo.Description = info?.Description;
            options.SwaggerDoc(f.Name, openApiInfo);
            groups.Add(f.Name);
        });

        // add a swagger document for each discovered API version
        // note: you might choose to skip or document deprecated API versions differently
        foreach (var description in provider.ApiVersionDescriptions)
        {
            //判断一下，避免重复添加
            if (!groups.Contains(description.GroupName))
                options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
        }

        //判断接口归于哪个分组
        options.DocInclusionPredicate((docName, apiDescription) =>
        {
            bool isInclude = false;
            if(!apiInclus.TryGetValue((docName, apiDescription.ActionDescriptor.Id), out isInclude))
            {
                isInclude = InclusionPredicate(docName, apiDescription);
                apiInclus.Add((docName, apiDescription.ActionDescriptor.Id), isInclude);
            }

            return isInclude;
        });

        #endregion

    }

    static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
    {
        var openApiInfo = ServiceHost.Current.GetService<IOptions<OpenApiInfo>>().Value;

        var info = new OpenApiInfo()
        {
            Title = openApiInfo.Title,
            Version = description.ApiVersion.ToString(),
            Description = openApiInfo.Description,
            Contact = openApiInfo.Contact,
            License = openApiInfo.License
        };

        if (description.IsDeprecated)
        {
            info.Description += " This API version has been deprecated.";
        }

        return info;
    }

    //判断接口归于哪个分组
    static bool InclusionPredicate(string docName, ApiDescription apiDescription)
    {
        if (!apiDescription.TryGetMethodInfo(out MethodInfo method)) return false;

        //反射拿到控制器分组特性下的值
        var apiGroup = apiDescription.ActionDescriptor.EndpointMetadata.Where(x => x is ApiGroupAttribute).Select(o => (ApiGroupAttribute)o);
        //如果有IgnoreApi标记，则不在swagger文档中显示
        if (apiGroup.Any(o => o.IgnoreApi)) return false;

        //全部接口
        if (docName == "All") return true;

        /*
         * 情况一：docName为Microsoft.AspNetCore.Mvc.Versioning组件产生的分组，api通过ApiGroupAttribute定义分组时
         * 情况二：docName为ApiGroupAttribute定义的分组，api通过ApiGroupAttribute定义分组时
         */
        if (docName == apiDescription.GroupName) return true;

        //docName为Microsoft.AspNetCore.Mvc.Versioning组件产生的分组，api通过ApiExplorerSettings分组时
        if (apiDescription.RelativePath.StartsWith($"api/{docName}/")) return true;

        //获取分组信息
        var groupNames = apiGroup.Where(o => !o.IgnoreApi).Select(o => o.GroupNames);

        //得到尚未分组的接口***************
        if (docName == "NoGroup") return groupNames.Count() == 0 && apiDescription.RelativePath.StartsWith($"api/{apiDescription.GroupName}/"); //后面这个判断是为了排除ApiExplorerSettings分组

        //加载对应已经分好组的接口
        if (groupNames.Count() != 0)
        {
            //判断是否包含这个分组
            return groupNames.Any(o => o.Any(x => x.ToString() == docName));
        }

        return false;
    }
}
